﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Radovici.SharedLibrary;

namespace Radovici.Ctrls.ContentControls
{
    [TemplateVisualState(Name = "MouseOverDropable", GroupName = "InteractiveStates")]
    [TemplateVisualState(Name = "Dropable", GroupName = "InteractiveStates")]
    [TemplateVisualState(Name = "Normal", GroupName = "InteractiveStates")]
    public class DropTarget : ContentControl
    {
        #region Private static fields
        private static List<WeakReference> _targets = new List<WeakReference>(); //TODO: Revert to hash if list becomes too large; this approach is easier (no book keeping for old/new keys).
        #endregion

        public static void Monitor(Dropable source)
        {
            source.DropStarted += new Action<Dropable>(OnDropStarted); //TODO: Consider starting on dragging as well (as a safeguard).
            source.Dragging += new Action<Dragable, MouseEventArgs>(OnDragging);
            source.DropStopped += new Action<Dropable>(OnDropStopped);
        }

        #region Private static methods
        private static void Register(DropTarget target)
        {
            _targets.Add(new WeakReference(target));
        }

        private static void OnDropStarted(Dropable obj)
        {            
            foreach (DropTarget target in GetDropTargets(obj))
            {
                target.IsDropActive = true;
            }
        }

        private static void OnDragging(Dragable obj, MouseEventArgs e)
        {
            foreach (DropTarget target in GetDropTargets(obj as Dropable))
            {
                if (target.ActualHeight != 0 && target.ActualWidth != 0)
                {
                    Point mousePosition = e.GetPosition(target);
                    if (mousePosition.X > 0 && mousePosition.X < target.ActualWidth && mousePosition.Y > 0 && mousePosition.Y < target.ActualHeight)
                    {
                        target.IsMouseOver = true; //NOTE: For diagnostic purposes; could set IsMouseOver to the boolean condition.
                    }
                    else
                    {
                        target.IsMouseOver = false;
                    }
                }
            }
        }

        private static void OnDropStopped(Dropable obj)
        {
            DropTarget foundTarget = null;
            foreach (DropTarget target in GetDropTargets(obj))
            {
                target.IsDropActive = false;
                if (target.IsMouseOver)
                {
                    if (foundTarget != null)
                    {
                        if (target.ActualHeight * target.ActualWidth < foundTarget.ActualHeight * foundTarget.ActualWidth)
                        {
                            foundTarget = target;
                        }
                    }
                    else
                    {
                        foundTarget = target;
                    }                    
                }                
            }
            if (foundTarget != null) foundTarget.Drop(obj);
            obj.DropStarted -= new Action<Dropable>(OnDropStarted);
            obj.DropStopped -= new Action<Dropable>(OnDropStopped);
            _lastDropable = null;
            _lastDropableCache = null;
        }

        private static Dropable _lastDropable;
        private static IList<DropTarget> _lastDropableCache;
        private static IEnumerable<DropTarget> GetDropTargets(Dropable obj)
        {
            if (_lastDropable == obj && _lastDropableCache != null) return _lastDropableCache;
            _lastDropable = obj;
            _lastDropableCache = new List<DropTarget>();
            foreach (var weakRef in _targets)
            {
                if (weakRef.IsAlive)
                {
                    var drop = weakRef.Target as DropTarget;
                    if (drop != null)
                    {
                        if (drop.TargetDataContext != null)
                        {
                            Debug.WriteLine(string.Format("DropTarget:GetDropTargets(...), {0}", drop.TargetDataContext));
                            bool isChildOf = obj.IsChildOf(drop, int.MaxValue);
                            if (!isChildOf || drop.IsChildOf(Application.Current.RootVisual, 3))
                            {
                                MethodInfo method = drop.TargetDataContext.GetType().GetMethod(drop.Function, new Type[] { obj.Source.GetType() });
                                if (drop.DropEnabled && method != null)
                                {
                                    _lastDropableCache.Add(drop);
                                }
                            }
                        }
                    }
                }
                else
                {
                    Debug.WriteLine("DropTarget:GetDropTargets, WeakReference not alive");
                }
            }
            return _lastDropableCache;
        }
        #endregion

        #region Private fields
        private bool _isMouseOver;
        private bool _isDropActive;
        #endregion

        public DropTarget()
        {
            DefaultStyleKey = typeof(DropTarget);
            Register(this);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            UpdateStates(false);
        }

        private void UpdateStates(bool useTransitions)
        {
            if (IsDropActive)
            {
                if (IsMouseOver)
                {
                    VisualStateManager.GoToState(this, "MouseOverDropable", useTransitions);
                }
                else
                {
                    VisualStateManager.GoToState(this, "Dropable", useTransitions);
                }
                return;
            }
            VisualStateManager.GoToState(this, "Normal", useTransitions);
        }

        #region TargetDataContext
        public object TargetDataContext
        {
            get { return (object)GetValue(TargetProperty); }
            set { SetValue(TargetProperty, value); }
        }

        // Using a DependencyProperty as the backing store for TargetDataContext.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TargetProperty = DependencyProperty.Register("TargetDataContext", typeof(object), typeof(DropTarget), new PropertyMetadata(null));
        #endregion

        #region DropEnabled
        public bool DropEnabled
        {
            get { return (bool)GetValue(DropEnabledProperty); }
            set { SetValue(DropEnabledProperty, value); }
        }

        // Using a DependencyProperty as the backing store for DropEnabled.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DropEnabledProperty = DependencyProperty.Register("DropEnabled", typeof(bool), typeof(DropTarget), new PropertyMetadata(true, OnDropEnabledChanged));

        private static void OnDropEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        }
        #endregion

        public string Function
        {
            get { return (string)GetValue(FunctionProperty); }
            set { SetValue(FunctionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Function.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FunctionProperty = DependencyProperty.Register("Function", typeof(string), typeof(DropTarget), new PropertyMetadata("Add"));

        public bool IsDropActive
        {
            get { return _isDropActive; }
            private set
            {
                _isDropActive = value;
                UpdateStates(true);
            }
        }

        public bool IsMouseOver
        {
            get { return _isMouseOver; }
            private set 
            { 
                _isMouseOver = value;
                UpdateStates(true);
            }
        }

        public void Drop(Dropable obj)
        {
            if (TargetDataContext != null)
            {
                MethodInfo method = TargetDataContext.GetType().GetMethod(Function, new Type[] { obj.Source.GetType() });
                if (method != null)
                {
                    method.Invoke(TargetDataContext, new object[] { obj.Source });
                }
            }
        }

        protected override void OnMouseEnter(MouseEventArgs e) //TODO: Will test work? What if it's beneath another control? How about IsMouseCapture?
        {
            base.OnMouseEnter(e);
            _isMouseOver = true;
        }

        protected override void OnMouseLeave(MouseEventArgs e)
        {
            base.OnMouseLeave(e);
            _isMouseOver = false;
        }
    }
}
